package com.gorillalogic.scrummonkey.test {
	import com.gorillalogic.scrummonkey.dms.DmsUtils;
	import com.gorillalogic.scrummonkey.dms.Iteration;
	import com.gorillalogic.scrummonkey.dms.IterationService;
	import com.gorillalogic.scrummonkey.dms.Objective;
	import com.gorillalogic.scrummonkey.dms.ObjectiveService;
	import com.gorillalogic.scrummonkey.dms.Project;
	import com.gorillalogic.scrummonkey.dms.ProjectService;
	import com.gorillalogic.scrummonkey.dms.Release;
	import com.gorillalogic.scrummonkey.dms.ReleaseService;
	
	import mx.collections.ArrayCollection;
	
	import org.flexunit.Assert;
	import org.hamcrest.assertThat;
	import org.hamcrest.collection.hasItems;

	/**
	 * Tests the following:
	 *   A: O on I -> I in same R
	 *   B: O on R -> R
	 *   C: O on I -> parent R
	 *   D: O on I -> I in diff R
	 *  iA: I with O's -> R
	 *   E: O on R -> I in same R (aka child I)
	 *  iB: I without O's -> R
	 *   F: O on R -> I in diff R
	 *   G: O on I -> other R (non-parent)
	 */
	public class DragAndDropTest extends BaseTest {

		/**
		 * Create hierarchy: P,R1,R2,I1,I2,I3
		 * Create objectives: O1 on I1, O2 on R1, O3 on I3
		 */
		[Test(async,order=1)]
		public function create():void {
			var p:Project = new Project('proj1');
			var r1:Release = new Release('rel1');
			var r2:Release = new Release('rel2');
			var i1:Iteration = new Iteration('it1');
			var i2:Iteration = new Iteration('it2');
			var i3:Iteration = new Iteration('it3');
			var o1:Objective = new Objective('obj1');
			var o2:Objective = new Objective('obj2');
			var o3:Objective = new Objective('obj3');

			//wireup hierarchy
			p.releases.addItem(r1);
			p.releases.addItem(r2);

			r1.project = p;
			r2.project = p;

			r1.iterations.addItem(i1);
			r1.iterations.addItem(i2);
			r2.iterations.addItem(i3);

			i1.release = r1;
			i2.release = r1;
			i3.release = r2;


			//wireup objectives on project
			p.objectives.addItem(o1);
			p.objectives.addItem(o2);
			p.objectives.addItem(o3);

			o1.project = p;
			o2.project = p;
			o3.project = p;

			//wireup objectives on releases
			r1.objectives.addItem(o1);
			r1.objectives.addItem(o2);
			r2.objectives.addItem(o3);

			o1.release = r1;
			o2.release = r1;
			o3.release = r2;

			//wireup objectives on iterations
			i1.objectives.addItem(o1);
			i3.objectives.addItem(o3);

			o1.iteration = i1;
			o3.iteration = i3;

			callAndThenOrFault(ProjectService.service.createProject(p), create2);
		}

		private function create2(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ProjectService.service.getByName('proj1'), create3);
		}

		private function create3(data:Object, passThroughData:Object):void {
			var projects:ArrayCollection = data.result as ArrayCollection;
			Assert.assertEquals('Bad number of projects', 1, projects.length);

			//verify project
			var project:Project = projects.getItemAt(0) as Project;
			Assert.assertEquals('Bad project name', 'proj1', project.name);

			//verify release
			Assert.assertEquals('Bad number of releases', 2, project.releases.length);
			var r1:Release = project.releases.getItemAt(0) as Release;
			var r2:Release = project.releases.getItemAt(1) as Release;
			assertThat('Bad release names', [r1.name, r2.name], hasItems('rel1','rel2'));

			//verify objective
			Assert.assertEquals('Bad number of objectives on project', 3, project.objectives.length);
			var o1:Objective = project.objectives.getItemAt(0) as Objective;
			var o2:Objective = project.objectives.getItemAt(1) as Objective;
			var o3:Objective = project.objectives.getItemAt(2) as Objective;
			assertThat('Bad objective names', [o1.name, o2.name, o3.name], hasItems('obj1','obj2','obj3'));
			Assert.assertEquals("Bad objective's project", 'proj1', o1.project.name);
			Assert.assertEquals("Bad objective's project", 'proj1', o2.project.name);
			Assert.assertEquals("Bad objective's project", 'proj1', o3.project.name);

			callAndThenOrFault(ReleaseService.service.getByName('rel1'), create4);
			callAndThenOrFault(ReleaseService.service.getByName('rel2'), create5);
		}

		private function create4(data:Object, passThroughData:Object):void {
			var releases:ArrayCollection = data.result as ArrayCollection;
			Assert.assertEquals('Bad number of releases', 1, releases.length);

			//verify release
			var release:Release = releases.getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);
			Assert.assertEquals("Bad release's project name", 'proj1', release.project.name);

			//verify iteration
			Assert.assertEquals('Bad number of iterations', 2, release.iterations.length);
			var i1:Iteration = release.iterations.getItemAt(0) as Iteration;
			var i2:Iteration = release.iterations.getItemAt(1) as Iteration;
			assertThat('Bad iteration names', [i1.name, i2.name], hasItems('it1','it2'));
			Assert.assertEquals("Bad iteration's release name", 'rel1', i1.release.name);
			Assert.assertEquals("Bad iteration's release name", 'rel1', i2.release.name);
			
			//verify objectives
			Assert.assertEquals('Bad number of objectives on release', 2, release.objectives.length);
		}

		private function create5(data:Object, passThroughData:Object):void {
			var releases:ArrayCollection = data.result as ArrayCollection;
			Assert.assertEquals('Bad number of releases', 1, releases.length);

			//verify release
			var release:Release = releases.getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel2', release.name);
			Assert.assertEquals("Bad release's project name", 'proj1', release.project.name);

			//verify iteration
			Assert.assertEquals('Bad number of iterations', 1, release.iterations.length);
			var i:Iteration = release.iterations.getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it3', i.name);
			Assert.assertEquals("Bad iteration's release name", 'rel2', i.release.name);

			//verify objectives
			Assert.assertEquals('Bad number of objectives on release', 1, release.objectives.length);
		}


		/**
		 * Drag-n-drop O (on I) to another I in same R
		 */
		[Test(async,order=2)]
		public function dropObjectiveA1():void {
			callAndThenOrFault(IterationService.service.getByName('it1'), dropObjectiveA2);
		}

		private function dropObjectiveA2(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it1', iteration.name);
			Assert.assertEquals('Bad release name', 'rel1', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 1, iteration.objectives.length);
			var objective:Objective = iteration.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj1', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertEquals("Bad objective's release", 'it1', objective.iteration.name);

			callAndThenOrFault(ReleaseService.service.getByName('rel1'), dropObjectiveA3, null, 5000, objective);
		}

		private function dropObjectiveA3(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);

			var srcObjective:Objective = passThroughData as Objective;
			Assert.assertEquals('Bad objective name', 'obj1', srcObjective.name);

			Assert.assertEquals('Bad number of iterations on release', 2, release.iterations.length);
			var i1:Iteration = release.iterations.getItemAt(0) as Iteration;
			var i2:Iteration = release.iterations.getItemAt(1) as Iteration;
			assertThat('Bad iteration names', [i1.name, i2.name], hasItems('it1','it2'));

			var targetIteration:Iteration = (i1.name == 'it2' ? i1 : i2);

			//Normally, we can just call objective.assign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ReleaseService.service,
				function ():void {
					srcObjective.assign(targetIteration);
				}), dropObjectiveA4);
		}

		private function dropObjectiveA4(data:Object, passThroughData:Object):void {
			callAndThenOrFault(IterationService.service.getByName('it1'), dropObjectiveA5);
			callAndThenOrFault(IterationService.service.getByName('it2'), dropObjectiveA6);
		}

		private function dropObjectiveA5(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it1', iteration.name);
			Assert.assertEquals('Bad release name', 'rel1', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 0, iteration.objectives.length);
		}

		private function dropObjectiveA6(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it2', iteration.name);
			Assert.assertEquals('Bad release name', 'rel1', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 1, iteration.objectives.length);
			var objective:Objective = iteration.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj1', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertEquals("Bad objective's iteration", 'it2', objective.iteration.name);
		}


		/**
		 * Drop objective (on release, but not iteration) to another release
		 */
		[Test(async,order=3)]
		public function dropObjectiveB1():void {
			callAndThenOrFault(ReleaseService.service.getByName('rel1'), dropObjectiveB2);
		}

		private function dropObjectiveB2(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);

			Assert.assertEquals('Bad number of objectives on release', 2, release.objectives.length);
			var o1:Objective = release.objectives.getItemAt(0) as Objective;
			var o2:Objective = release.objectives.getItemAt(1) as Objective;
			assertThat('Bad objective names', [o1.name, o2.name], hasItems('obj1','obj2'));

			var objective:Objective = (o1.name == 'obj2' ? o1 : o2);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertNull("Bad objective's iteration", objective.iteration);

			callAndThenOrFault(ReleaseService.service.getByName('rel2'), dropObjectiveB3, null, 5000, objective);
		}

		private function dropObjectiveB3(data:Object, passThroughData:Object):void {
			var targetRelease:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel2', targetRelease.name);

			var srcObjective:Objective = passThroughData as Objective;
			Assert.assertEquals('Bad objective name', 'obj2', srcObjective.name);

			//Normally, we can just call objective.assign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ReleaseService.service,
				function ():void {
					srcObjective.assign(targetRelease);
				}), dropObjectiveB4);
		}

		private function dropObjectiveB4(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ReleaseService.service.getByName('rel2'), dropObjectiveB5);
		}

		private function dropObjectiveB5(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel2', release.name);

			Assert.assertEquals('Bad number of objectives on release', 2, release.objectives.length);
			var o1:Objective = release.objectives.getItemAt(0) as Objective;
			var o2:Objective = release.objectives.getItemAt(1) as Objective;
			assertThat('Bad objective names', [o1.name, o2.name], hasItems('obj2','obj3'));

			var movedObjective:Objective = (o1.name == 'obj2' ? o1 : o2);

			Assert.assertEquals("Bad objective's project", 'proj1', movedObjective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel2', movedObjective.release.name);
			Assert.assertNull("Bad objective's iteration", movedObjective.iteration);
		}


		/**
		 * Drop objective (on iteration) onto parent release
		 */
		[Test(async,order=4)]
		public function dropObjectiveC1():void {
			callAndThenOrFault(IterationService.service.getByName('it2'), dropObjectiveC2);
		}

		private function dropObjectiveC2(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it2', iteration.name);
			Assert.assertEquals('Bad release name', 'rel1', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 1, iteration.objectives.length);
			var objective:Objective = iteration.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj1', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertEquals("Bad objective's release", 'it2', objective.iteration.name);

			callAndThenOrFault(ReleaseService.service.getByName('rel1'), dropObjectiveC3, null, 5000, objective);
		}

		private function dropObjectiveC3(data:Object, passThroughData:Object):void {
			var targetRelease:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', targetRelease.name);

			var srcObjective:Objective = passThroughData as Objective;
			Assert.assertEquals('Bad objective name', 'obj1', srcObjective.name);

			//Normally, we can just call objective.assign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ReleaseService.service,
				function ():void {
					srcObjective.assign(targetRelease);
				}), dropObjectiveC4);
		}

		private function dropObjectiveC4(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ReleaseService.service.getByName('rel1'), dropObjectiveC5);
		}

		private function dropObjectiveC5(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);

			Assert.assertEquals('Bad number of objectives on release', 1, release.objectives.length);
			var objective:Objective = release.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj1', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertNull("Bad objective's iteration", objective.iteration);
		}


		/**
		 * Drop objective (on iteration) to another iteration in different release
		 */
		[Test(async,order=5)]
		public function dropObjectiveD1():void {
			callAndThenOrFault(IterationService.service.getByName('it3'), dropObjectiveD2);
		}

		private function dropObjectiveD2(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it3', iteration.name);
			Assert.assertEquals('Bad release name', 'rel2', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 1, iteration.objectives.length);
			var objective:Objective = iteration.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj3', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel2', objective.release.name);
			Assert.assertEquals("Bad objective's release", 'it3', objective.iteration.name);

			callAndThenOrFault(IterationService.service.getByName('it1'), dropObjectiveD3, null, 5000, objective);
		}

		private function dropObjectiveD3(data:Object, passThroughData:Object):void {
			var targetIteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it1', targetIteration.name);
			Assert.assertEquals('Bad release name', 'rel1', targetIteration.release.name);

			var srcObjective:Objective = passThroughData as Objective;
			Assert.assertEquals('Bad objective name', 'obj3', srcObjective.name);

			//Normally, we can just call objective.assign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ReleaseService.service,
				function ():void {
					srcObjective.assign(targetIteration);
				}), dropObjectiveD4);
		}

		private function dropObjectiveD4(data:Object, passThroughData:Object):void {
			callAndThenOrFault(IterationService.service.getByName('it3'), dropObjectiveD5);
			callAndThenOrFault(IterationService.service.getByName('it1'), dropObjectiveD6);
		}

		private function dropObjectiveD5(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it3', iteration.name);
			Assert.assertEquals('Bad release name', 'rel2', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 0, iteration.objectives.length);
		}

		private function dropObjectiveD6(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it1', iteration.name);
			Assert.assertEquals('Bad release name', 'rel1', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 1, iteration.objectives.length);
			var objective:Objective = iteration.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj3', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertEquals("Bad objective's iteration", 'it1', objective.iteration.name);
		}


		/**
		 * Drop iteration (with child objective) on another release
		 */
		[Test(async,order=6)]
		public function dropIterationA1():void {
			//get project by name & wait for response
			callAndThenOrFault(IterationService.service.getByName('it1'), dropIterationA2);
		}

		private function dropIterationA2(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it1', iteration.name);
			Assert.assertEquals('Bad release name', 'rel1', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 1, iteration.objectives.length);
			var objective:Objective = iteration.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj3', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertEquals("Bad objective's release", 'it1', objective.iteration.name);

			callAndThenOrFault(ReleaseService.service.getByName('rel2'), dropIterationA3, null, 5000, iteration);
		}

		private function dropIterationA3(data:Object, passThroughData:Object):void {
			var targetRelease:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel2', targetRelease.name);

			Assert.assertEquals('Bad number of iterations on release', 1, targetRelease.iterations.length);
			var iteration:Iteration = targetRelease.iterations.getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it3', iteration.name);

			var srcIteration:Iteration = passThroughData as Iteration;
			Assert.assertEquals('Bad iteration name', 'it1', srcIteration.name);

			//Normally, we can just call iteration.assign(release) and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ReleaseService.service,
				function ():void {
					srcIteration.assign(targetRelease);
				}), dropIterationA4);
		}

		private function dropIterationA4(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ReleaseService.service.getByName('rel1'), dropIterationA5);
			callAndThenOrFault(ReleaseService.service.getByName('rel2'), dropIterationA6);
			callAndThenOrFault(IterationService.service.getByName('it1'), dropIterationA7);
			callAndThenOrFault(ObjectiveService.service.getByName('obj3'), dropIterationA8);
		}

		private function dropIterationA5(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);

			Assert.assertEquals('Bad number of iterations on release', 1, release.iterations.length);
			var iteration:Iteration = release.iterations.getItemAt(0) as Iteration;
			Assert.assertEquals('Bad objective name', 'it2', iteration.name);

			Assert.assertEquals('Bad number of objectives on release', 1, release.objectives.length);
			var objective:Objective = release.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj1', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertNull("Bad objective's iteration", objective.iteration);
		}

		private function dropIterationA6(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel2', release.name);

			Assert.assertEquals('Bad number of iterations on release', 2, release.iterations.length);
			var i1:Iteration = release.iterations.getItemAt(0) as Iteration;
			var i2:Iteration = release.iterations.getItemAt(1) as Iteration;
			assertThat('Bad iteration names', [i1.name, i2.name], hasItems('it1','it3'));

			Assert.assertEquals('Bad number of objectives on release', 2, release.objectives.length);
		}

		private function dropIterationA7(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it1', iteration.name);
			Assert.assertEquals('Bad release name', 'rel2', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 1, iteration.objectives.length);
			var objective:Objective = iteration.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj3', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel2', objective.release.name);
			Assert.assertEquals("Bad objective's iteration", 'it1', objective.iteration.name);
		}

		private function dropIterationA8(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj3', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel2', objective.release.name);
			Assert.assertEquals("Bad objective's iteration", 'it1', objective.iteration.name);
		}


		/**
		 * Drop objective (on release, but not iteration) to iteration on same release
		 */
		[Test(async,order=7)]
		public function dropObjectiveE1():void {
			callAndThenOrFault(ReleaseService.service.getByName('rel1'), dropObjectiveE2);
		}

		private function dropObjectiveE2(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);

			Assert.assertEquals('Bad number of iterations on release', 1, release.iterations.length);
			var iteration:Iteration = release.iterations.getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it2', iteration.name);

			Assert.assertEquals('Bad number of objectives on release', 1, release.objectives.length);
			var objective:Objective = release.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj1', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertNull("Bad objective's iteration", objective.iteration);

			//Normally, we can just call objective.assign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ObjectiveService.service,
				function ():void {
					objective.assign(iteration);
				}), dropObjectiveE3);
		}

		private function dropObjectiveE3(data:Object, passThroughData:Object):void {
			callAndThenOrFault(IterationService.service.getByName('it2'), dropObjectiveE4);
		}

		private function dropObjectiveE4(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it2', iteration.name);

			Assert.assertEquals('Bad number of objectives on iteration', 1, iteration.objectives.length);
			var objective:Objective = iteration.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj1', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertEquals("Bad objective's iteration", 'it2', objective.iteration.name);
		}


		/**
		 * Drop iteration (with NO child objective) on another release
		 */
		[Test(async,order=8)]
		public function dropIterationB1():void {
			//get project by name & wait for response
			callAndThenOrFault(IterationService.service.getByName('it3'), dropIterationB2);
		}

		private function dropIterationB2(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it3', iteration.name);
			Assert.assertEquals('Bad release name', 'rel2', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 0, iteration.objectives.length);

			callAndThenOrFault(ReleaseService.service.getByName('rel1'), dropIterationB3, null, 5000, iteration);
		}

		private function dropIterationB3(data:Object, passThroughData:Object):void {
			var targetRelease:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', targetRelease.name);

			Assert.assertEquals('Bad number of iterations on release', 1, targetRelease.iterations.length);
			var iteration:Iteration = targetRelease.iterations.getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it2', iteration.name);

			Assert.assertEquals('Bad number of objectives on release', 1, targetRelease.objectives.length);

			var srcIteration:Iteration = passThroughData as Iteration;
			Assert.assertEquals('Bad iteration name', 'it3', srcIteration.name);

			//Normally, we can just call iteration.assign(release) and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ReleaseService.service,
				function ():void {
					srcIteration.assign(targetRelease);
				}), dropIterationB4);
		}

		private function dropIterationB4(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ReleaseService.service.getByName('rel1'), dropIterationB5);
			callAndThenOrFault(ReleaseService.service.getByName('rel2'), dropIterationB6);
			callAndThenOrFault(IterationService.service.getByName('it3'), dropIterationB7);
		}

		private function dropIterationB5(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel1', release.name);

			Assert.assertEquals('Bad number of iterations on release', 2, release.iterations.length);
			var i1:Iteration = release.iterations.getItemAt(0) as Iteration;
			var i2:Iteration = release.iterations.getItemAt(1) as Iteration;
			assertThat('Bad iteration names', [i1.name, i2.name], hasItems('it2','it3'));

			Assert.assertEquals('Bad number of objectives on release', 1, release.objectives.length);
		}

		private function dropIterationB6(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel2', release.name);

			Assert.assertEquals('Bad number of iterations on release', 1, release.iterations.length);
			var iteration:Iteration = release.iterations.getItemAt(0) as Iteration;
			Assert.assertEquals('Bad objective name', 'it1', iteration.name);

			Assert.assertEquals('Bad number of objectives on release', 2, release.objectives.length);
		}

		private function dropIterationB7(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it3', iteration.name);
			Assert.assertEquals('Bad release name', 'rel1', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 0, iteration.objectives.length);
		}


		/**
		 * Drop objective (on release, but not iteration) to iteration on different release
		 */
		[Test(async,order=9)]
		public function dropObjectiveF1():void {
			callAndThenOrFault(IterationService.service.getByName('it3'), dropObjectiveF2);
		}

		private function dropObjectiveF2(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it3', iteration.name);
			Assert.assertEquals('Bad release name', 'rel1', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 0, iteration.objectives.length);

			callAndThenOrFault(ObjectiveService.service.getByName('obj2'), dropObjectiveF3, null, 5000, iteration);
		}

		private function dropObjectiveF3(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj2', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel2', objective.release.name);
			Assert.assertNull("Bad objective's iteration", objective.iteration);

			var targetIteration:Iteration = passThroughData as Iteration;
			Assert.assertEquals('Bad iteration name', 'it3', targetIteration.name);

			//Normally, we can just call objective.assign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ObjectiveService.service,
				function ():void {
					objective.assign(targetIteration);
				}), dropObjectiveF4);
		}

		private function dropObjectiveF4(data:Object, passThroughData:Object):void {
			callAndThenOrFault(IterationService.service.getByName('it3'), dropObjectiveF5);
		}

		private function dropObjectiveF5(data:Object, passThroughData:Object):void {
			var iteration:Iteration = (data.result as ArrayCollection).getItemAt(0) as Iteration;
			Assert.assertEquals('Bad iteration name', 'it3', iteration.name);
			Assert.assertEquals('Bad release name', 'rel1', iteration.release.name);

			Assert.assertEquals('Bad number of objectives on iteration', 1, iteration.objectives.length);
			var objective:Objective = iteration.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj2', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertEquals("Bad objective's iteration", 'it3', objective.iteration.name);
		}


		/**
		 * Drop objective (on iteration) to other non-parent release
		 */
		[Test(async,order=10)]
		public function dropObjectiveG1():void {
			callAndThenOrFault(ReleaseService.service.getByName('rel2'), dropObjectiveG2);
		}

		private function dropObjectiveG2(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel2', release.name);

			Assert.assertEquals('Bad number of objectives on release', 1, release.objectives.length);
			var objective:Objective = release.objectives.getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj3', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel2', objective.release.name);
			Assert.assertEquals("Bad objective's iteration", 'it1', objective.iteration.name);

			callAndThenOrFault(ObjectiveService.service.getByName('obj1'), dropObjectiveG3, null, 5000, release);
		}

		private function dropObjectiveG3(data:Object, passThroughData:Object):void {
			var objective:Objective = (data.result as ArrayCollection).getItemAt(0) as Objective;
			Assert.assertEquals('Bad objective name', 'obj1', objective.name);
			Assert.assertEquals("Bad objective's project", 'proj1', objective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel1', objective.release.name);
			Assert.assertEquals("Bad objective's iteration", 'it2', objective.iteration.name);

			var targetRelease:Release = passThroughData as Release;
			Assert.assertEquals('Bad iteration name', 'rel2', targetRelease.name);

			//Normally, we can just call objective.assign() and forget about it,
			//but if we must guarantee completion...then use a transaction
			callAndThenOrFault(DmsUtils.doTxn(ObjectiveService.service,
				function ():void {
					objective.assign(targetRelease);
				}), dropObjectiveG4);
		}

		private function dropObjectiveG4(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ReleaseService.service.getByName('rel2'), dropObjectiveG5);
		}

		private function dropObjectiveG5(data:Object, passThroughData:Object):void {
			var release:Release = (data.result as ArrayCollection).getItemAt(0) as Release;
			Assert.assertEquals('Bad release name', 'rel2', release.name);

			Assert.assertEquals('Bad number of objectives on release', 2, release.objectives.length);
			var o1:Objective = release.objectives.getItemAt(0) as Objective;
			var o2:Objective = release.objectives.getItemAt(1) as Objective;
			assertThat('Bad objective names', [o1.name, o2.name], hasItems('obj1','obj3'));

			var movedObjective:Objective = (o1.name == 'obj1' ? o1 : o2);
			Assert.assertEquals("Bad objective's project", 'proj1', movedObjective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel2', movedObjective.release.name);
			Assert.assertNull("Bad objective's iteration", movedObjective.iteration);

			var otherObjective:Objective = (o1.name == 'obj3' ? o1 : o2);
			Assert.assertEquals("Bad objective's project", 'proj1', otherObjective.project.name);
			Assert.assertEquals("Bad objective's release", 'rel2', otherObjective.release.name);
			Assert.assertEquals("Bad objective's iteration", 'it1', otherObjective.iteration.name);
		}


		/**
		 * Cleanup everything
		 */
		[Test(async,order=11)]
		public function cleanup():void {
			callAndThenOrFault(ProjectService.service.getByName('proj1'), cleanup2);
		}

		private function cleanup2(data:Object, passThroughData:Object):void {
			var project:Project = (data.result as ArrayCollection).getItemAt(0) as Project;
			Assert.assertEquals('Bad project name', 'proj1', project.name);

			callAndThenOrFault(ProjectService.service.deleteProject(project), cleanup3);
		}

		private function cleanup3(data:Object, passThroughData:Object):void {
			callAndThenOrFault(ProjectService.service.getByName('proj1'), cleanup4);
			callAndThenOrFault(ReleaseService.service.getByName('rel1'), cleanup4);
			callAndThenOrFault(ReleaseService.service.getByName('rel2'), cleanup4);
			callAndThenOrFault(IterationService.service.getByName('it1'), cleanup4);
			callAndThenOrFault(IterationService.service.getByName('it2'), cleanup4);
			callAndThenOrFault(IterationService.service.getByName('it3'), cleanup4);
			callAndThenOrFault(ObjectiveService.service.getByName('obj1'), cleanup4);
			callAndThenOrFault(ObjectiveService.service.getByName('obj2'), cleanup4);
			callAndThenOrFault(ObjectiveService.service.getByName('obj3'), cleanup4);
		}

		private function cleanup4(data:Object, passThroughData:Object):void {
			var entities:ArrayCollection = data.result as ArrayCollection;
			Assert.assertEquals('Cleanup failed', 0, entities.length);
		}
	}
}